/*
 * MegaMek -
 * Copyright (C) 2000,2001,2002,2003,2004,2005 Ben Mazur (bmazur@sev.org)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 */

package megamek.client.bot;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.Vector;

import megamek.common.AmmoType;
import megamek.common.BattleArmor;
import megamek.common.Compute;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.EquipmentType;
import megamek.common.IAimingModes;
import megamek.common.IEntityMovementType;
import megamek.common.IHex;
import megamek.common.Infantry;
import megamek.common.Mech;
import megamek.common.Minefield;
import megamek.common.MiscType;
import megamek.common.Mounted;
import megamek.common.MovePath;
import megamek.common.Protomech;
import megamek.common.TargetRoll;
import megamek.common.Terrains;
import megamek.common.ToHitData;
import megamek.common.WeaponType;
import megamek.common.actions.ChargeAttackAction;
import megamek.common.actions.DfaAttackAction;
import megamek.common.actions.EntityAction;
import megamek.common.actions.TorsoTwistAction;
import megamek.common.actions.WeaponAttackAction;
import megamek.common.containers.PlayerIDandList;
import megamek.common.event.GamePlayerChatEvent;

// TODO: Auto-generated Javadoc
/**
 * The Class TestBot.
 */
public class TestBot extends BotClient {

    /** The centities. */
    public CEntity.Table centities = new CEntity.Table(this);

    /** The chatp. */
    protected ChatProcessor chatp = new ChatProcessor();

    /** The ignore. */
    public int ignore = 10;

    /** The debug. */
    boolean debug = false;

    /** The enemies_moved. */
    int enemies_moved = 0;

    /** The old_moves. */
    GALance old_moves = null;

    /** The my_mechs_moved. */
    int my_mechs_moved = 0;

    /**
     * Instantiates a new test bot.
     *
     * @param name the name
     * @param host the host
     * @param port the port
     */
    public TestBot(String name, String host, int port) {
        super(name, host, port);
        ignore = config.getIgnoreLevel();
        debug = config.isDebug();
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#initialize()
     */
    public void initialize() {
        // removed
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#calculatePhysicalTurn()
     */
    public PhysicalOption calculatePhysicalTurn() {
        return PhysicalCalculator.calculatePhysicalTurn(this);
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#calculateMoveTurn()
     */
    public MovePath calculateMoveTurn() {
        long enter = System.currentTimeMillis();
        int initiative = 0;
        MoveOption min = null;

        System.out.println("beginning movement calculations...");

        // first check and that someone else has moved so we don't replan
        Object[] enemy_array = this.getEnemyEntities().toArray();
        for (int j = 0; j < enemy_array.length; j++) {
            if (!((Entity) enemy_array[j]).isSelectableThisTurn()) {
                initiative++;
            }
        }
        // if nobody's moved and we have a valid move waiting, use that
        if (initiative == enemies_moved && old_moves != null) {
            min = this.old_moves.getResult();
            if (min == null
                    || !min.isMoveLegal()
                    || (min.isPhysical && centities.get(min.getPhysicalTargetId()).isPhysicalTarget)) {
                this.old_moves = null;
                System.out.println("recalculating moves since the old move was invalid");
                return calculateMoveTurn();
            }
        } else {
            enemies_moved = initiative;
            ArrayList<MoveOption[]> possible = new ArrayList<MoveOption[]>();

            Enumeration<Entity> e = game.getEntities();

            while (e.hasMoreElements()) {
                Entity entity = e.nextElement();

                // ignore loaded and off-board units
                if (entity.getPosition() == null || entity.isOffBoard()) {
                    continue;
                }

                CEntity cen = centities.get(entity);
                cen.refresh();
                firstPass(cen);
            }

            Iterator<Entity> i = this.getEntitiesOwned().iterator();
            boolean short_circuit = false;

            while (i.hasNext() && !short_circuit) {
                Entity entity = i.next();

                // ignore loaded units
                // (not really necessary unless bot manages to load units)
                if (entity.getPosition() == null) {
                    continue;
                }

                // if we can't move this entity right now, ignore it
                if (!game.getTurn().isValidEntity(entity, game)) {
                    continue;
                }

                CEntity cen = centities.get(entity);

                System.out.println("Contemplating movement of " + entity.getShortName() + " "
                        + entity.getId());

                MoveOption[] result = calculateMove(entity);

                if (game.getOptions().booleanOption("skip_ineligable_movement")
                        && cen.getEntity().isImmobile()) {
                    cen.moved = true;
                } else if (!cen.moved) {
                    int j = 6;
					if (result.length < j) {
                        min = result.length > 0 ? (MoveOption) result[0] : null;
                        short_circuit = true;
                    }
                    possible.add(result);
                }
            }

            // should ignore mechs that are not engaged
            // and only do the below when there are 2 or mechs left to move
            if (!short_circuit) {
                if (this.getEntitiesOwned().size() > 1) {
                    int population = 50;
					int generations = 80;
					GALance lance = new GALance(this, possible, population, generations);
                    lance.evolve();
                    min = lance.getResult();
                    this.old_moves = lance;
                } else if (possible.get(0) != null && possible.get(0).length > 0) {
                    min = possible.get(0)[0];
                }
            }
        }
        if (min == null) {
            min = new MoveOption(game, centities.get(getFirstEntityNum()));
        }
        for (int d = 0; d < enemy_array.length; d++) {
            Entity en = (Entity) enemy_array[d];

            // ignore loaded units
            if (en.getPosition() == null) {
                continue;
            }

            CEntity enemy = centities.get(en);
            int enemy_hit_arc = CEntity.getThreatHitArc(enemy.current.getFinalCoords(),
                    enemy.current.getFinalFacing(), min.getFinalCoords());
            MoveOption.DamageInfo di = min.damageInfos.get(enemy);
            if (di != null) {
                enemy.expected_damage[enemy_hit_arc] += di.min_damage;
            }
            if (enemy.expected_damage[enemy_hit_arc] > 0) {
                enemy.hasTakenDamage = true;
            }
        }
        if (min.isPhysical) {
            centities.get(min.getPhysicalTargetId()).isPhysicalTarget = true;
        }
        System.out.println(min);
        min.getCEntity().current = min;
        min.getCEntity().last = min;
        this.my_mechs_moved++;
        min.getCEntity().moved = true;

        long exit = System.currentTimeMillis();
        System.out.println("move turn took " + (exit - enter) + " ms");

        // If this unit has a jammed RAC, and it has only walked,
        // add an unjam action
        if (min.getLastStep() != null) {
            if (min.getCEntity().entity.canUnjamRAC()) {
                if ((min.getLastStep().getMovementType() == IEntityMovementType.MOVE_WALK)
                        || (min.getLastStep().getMovementType() == IEntityMovementType.MOVE_VTOL_WALK)
                        || (min.getLastStep().getMovementType() == IEntityMovementType.MOVE_NONE)) {
                    // Cycle through all available weapons, only unjam if the
                    // jam(med)
                    // RACs count for a significant portion of possible damage
                    int rac_damage = 0;
                    int other_damage = 0;
                    int clearance_range = 0;
                    for (Mounted equip : min.getCEntity().entity.getWeaponList()) {
                        WeaponType test_weapon = new WeaponType();

                        test_weapon = (WeaponType) equip.getType();
                        if ((test_weapon.getAmmoType() == AmmoType.T_AC_ROTARY)
                                && (equip.isJammed() == true)) {
                            rac_damage = rac_damage + 4 * (test_weapon.getDamage());
                        } else {
                            if (equip.canFire()) {
                                other_damage += test_weapon.getDamage();
                                if (test_weapon.getMediumRange() > clearance_range) {
                                    clearance_range = test_weapon.getMediumRange();
                                }
                            }
                        }
                    }
                    // Even if the jammed RAC doesn't make up a significant
                    // portion
                    // of the units damage, its still better to have it
                    // functional
                    // If nothing is "close" then unjam anyways
                    int check_range = 100;
                    for (Enumeration<Entity> unit_selection = game.getEntities(); unit_selection
                            .hasMoreElements();) {
                        Entity enemy = unit_selection.nextElement();
                        if ((min.getCEntity().entity.getPosition() != null)
                                && (enemy.getPosition() != null)
                                && (enemy.isEnemyOf(min.getCEntity().entity))) {
                            if (enemy.isVisibleToEnemy()) {
                                if (min.getCEntity().entity.getPosition().distance(
                                        enemy.getPosition()) < check_range) {
                                    check_range = min.getCEntity().entity.getPosition().distance(
                                            enemy.getPosition());
                                }
                            }
                        }
                    }
                    if ((rac_damage >= other_damage) || (check_range < clearance_range)) {
                        min.addStep(MovePath.STEP_UNJAM_RAC);
                    }
                }
            }
        }

        return min;
    }

    /**
     * Calculate move.
     *
     * @param entity the entity
     * @return the move option[]
     */
    public MoveOption[] calculateMove(Entity entity) {
        ArrayList<Entity> enemy_array = new ArrayList<Entity>(game.getValidTargets(entity));
        ArrayList<Entity> entities = new ArrayList<Entity>(game.getEntitiesVector());
        CEntity self = centities.get(entity);
        MoveOption[] move_array;
        int friends = entities.size() - enemy_array.size();

        move_array = secondPass(self, friends, enemy_array, entities);
        // top balanced
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(1, 1), 50);
        // top damage
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(.5, 1), 50);

        move_array = thirdPass(self, enemy_array);

        // top balanced
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(1, 1), 30);
        // top damage
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(.5, 1), 30);

        // reduce self threat, and add bonus for terrain
        for (Iterator<MoveOption> i = self.pass.values().iterator(); i.hasNext();) {
            MoveOption option = i.next();
            option.setState();
            option.self_damage *= .5;
            option.self_threat *= .5;
            // TODO: should scale to the unit bv
            double terrain = 2 * ((double) Compute.getTargetTerrainModifier(game,
                    option.getEntity()).getValue());
            if (debug)
                option.tv.add(terrain + " Terrain Adjusment " + "\n");
            option.self_threat -= terrain;
        }

        move_array = fourthPass(self, enemy_array);
        // top balanced
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(1, 1), 20);
        // top damage
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(.5, 1), 20);

        // reduce transient damage estimates
        for (Iterator<MoveOption> i = self.pass.values().iterator(); i.hasNext();) {
            MoveOption option = i.next();
            option.self_threat *= .5;
            option.self_damage *= .5;
        }

        move_array = fifthPass(self, enemy_array);

        /*******************************************************************************************
         * Return top twenty moves to the lance algorithm
         ******************************************************************************************/
        MoveOption[] result = new MoveOption[Math.min(move_array.length, 20)];
        int offset = 0;
        for (int i = 0; i < Math.min(move_array.length, 20); i++) {
            MoveOption next = move_array[i];
            if (next.isPhysical && self.rangeDamages[CEntity.RANGE_SHORT] > 5 && next.doomed) {
                if (offset + 20 < move_array.length) {
                    next = move_array[offset + 20];
                    offset++;
                }
            }
            result[i] = next;
        }
        return result;
    }

    /**
     * ************************************************************************ first pass, filter
     * moves based upon present case
     * ************************************************************************.
     *
     * @param self the self
     */
    public void firstPass(CEntity self) {
        ArrayList<Entity> enemies = getEnemyEntities();
        MoveOption[] move_array;
        if (self.getEntity().isSelectableThisTurn() && !self.moved) {
            move_array = self.getAllMoves(this).values().toArray(new MoveOption[0]);
        } else {
            move_array = new MoveOption[] { self.current };
        }
        System.out
                .println(self.getEntity().getShortName() + " has " + move_array.length + " moves");
        for (int i = 0; i < move_array.length; i++) {
            MoveOption option = move_array[i];
            option.setState();
            for (int e = 0; e < enemies.size(); e++) { // for each enemy
                Entity en = enemies.get(e);

                // ignore loaded units
                if (en.getPosition() == null) {
                    continue;
                }

                CEntity enemy = centities.get(en);
                int[] modifiers = option.getModifiers(enemy.getEntity());
                if (modifiers[MoveOption.DEFENCE_MOD] == TargetRoll.IMPOSSIBLE
                        && modifiers[MoveOption.ATTACK_MOD] == TargetRoll.IMPOSSIBLE) {
                    continue;
                }
                int enemy_hit_arc = CEntity.getThreatHitArc(enemy.current.getFinalCoords(),
                        enemy.current.getFinalFacing(), option.getFinalCoords());
                int self_hit_arc = CEntity.getThreatHitArc(option.getFinalCoords(), option
                        .getFinalFacing(), enemy.current.getFinalCoords());
                if (!enemy.getEntity().isImmobile()
                        && modifiers[MoveOption.DEFENCE_MOD] != TargetRoll.IMPOSSIBLE) {
                    self.engaged = true;
                    int mod = modifiers[MoveOption.DEFENCE_MOD];
                    double max = option.getMaxModifiedDamage(enemy.current, mod,
                            modifiers[MoveOption.DEFENCE_PC]);
                    if (en.isSelectableThisTurn()) {
                        enemy.current.addStep(MovePath.STEP_TURN_RIGHT);
                        max = Math.max(option.getMaxModifiedDamage(enemy.current, mod + 1,
                                modifiers[MoveOption.DEFENCE_PC]), max);
                        enemy.current.removeLastStep();
                        enemy.current.addStep(MovePath.STEP_TURN_LEFT);
                        max = Math.max(option.getMaxModifiedDamage(enemy.current, mod + 1,
                                modifiers[MoveOption.DEFENCE_PC]), max);
                        // return to original facing
                        enemy.current.removeLastStep();
                    }
                    max = self.getThreatUtility(max, self_hit_arc);
                    if (enemy.getEntity().isProne())
                        max *= enemy.base_psr_odds;
                    MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                    di.threat = max;
                    di.max_threat = max;
                    option.threat += max;
                    if (debug)
                        option.tv.add(max + " Threat " + e + "\n");
                }
                /*
                 * As a first approximation, take the maximum to a single target
                 */
                if (!option.isPhysical) {
                    if (modifiers[MoveOption.ATTACK_MOD] != TargetRoll.IMPOSSIBLE) {
                        self.engaged = true;
                        double max = enemy.current.getMaxModifiedDamage(option, modifiers[0],
                                modifiers[MoveOption.ATTACK_PC]);
                        max = enemy.getThreatUtility(max, enemy_hit_arc);
                        MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                        di.damage = max;
                        di.min_damage = max;
                        if (debug)
                            option.tv.add(max + " Damage " + e + "\n");
                        option.damage = Math.max(max, option.damage);
                    }
                } else {
                    CEntity target = centities.get(option.getPhysicalTargetId());
                    try {
                        if (target.getEntity().getId() == enemy.getEntity().getId()) {
                            if (!target.isPhysicalTarget) {
                                ToHitData toHit = null;
                                double self_threat = 0;
                                double damage = 0;
                                if (option.isJumping() && option.getEntity().canDFA()) {
                                    self.current.setState();
                                    toHit = DfaAttackAction.toHit(game, option.getEntity().getId(),
                                            target.getEntity(), option);
                                    damage = 2 * DfaAttackAction
                                            .getDamageFor(
                                                    option.getEntity(),
                                                    target.getEntity() instanceof Infantry
                                                            && !(target.getEntity() instanceof BattleArmor));
                                    self_threat = option.getCEntity().getThreatUtility(
                                            DfaAttackAction.getDamageTakenBy(option.getEntity()),
                                            ToHitData.SIDE_REAR)
                                            * Compute.oddsAbove(toHit.getValue()) / 100;
                                    self_threat += option.getCEntity().getThreatUtility(
                                            .1 * self.getEntity().getWeight(), ToHitData.SIDE_REAR);
                                    self_threat *= 100 / option.getCEntity().getEntity()
                                            .getWeight();
                                } else if (option.getEntity().canCharge()) {
                                    self.current.setState();
                                    toHit = new ChargeAttackAction(option.getEntity(), target
                                            .getEntity()).toHit(game, option);
                                    damage = ChargeAttackAction.getDamageFor(option.getEntity(),
                                            target.getEntity(), false, option.getHexesMoved());
                                    self_threat = option.getCEntity().getThreatUtility(
                                            ChargeAttackAction.getDamageTakenBy(option.getEntity(),
                                                    target.getEntity()), ToHitData.SIDE_FRONT)
                                            * (Compute.oddsAbove(toHit.getValue()) / 100);
                                    option.setState();
                                }
                                damage = target.getThreatUtility(damage, toHit.getSideTable())
                                        * Compute.oddsAbove(toHit.getValue()) / 100;
                                // charging is a good tactic against larger
                                // mechs
                                if (!option.isJumping())
                                    damage *= Math.sqrt((double) enemy.bv / (double) self.bv);
                                // these are always risky, just don't on 11 or
                                // 12
                                if (toHit.getValue() > 10)
                                    damage = 0;
                                // 7 or less is good
                                if (toHit.getValue() < 8)
                                    damage *= 1.5;
                                // this is all you are good for
                                if (self.rangeDamages[CEntity.RANGE_SHORT] < 5)
                                    damage *= 2;
                                MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                                di.damage = damage;
                                di.min_damage = damage;
                                option.damage = damage;
                                option.movement_threat += self_threat;
                            } else {
                                option.threat += Integer.MAX_VALUE;
                            }
                        }
                    } catch (Exception e1) {
                        e1.printStackTrace();
                        option.threat += Integer.MAX_VALUE;
                    }
                }
            } // -- end while of each enemy
            self.current.setState();
        } // -- end while of first pass
        // top balanced
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(1, 1), 100);
        // top damage
        filterMoves(move_array, self.pass, new MoveOption.WeightedComparator(.5, 1), 100);
    }

    /**
     * ******************************************************************** Second pass, combination
     * moves/firing based only on the present case, since only one mech moves at a time
     * ********************************************************************.
     *
     * @param self the self
     * @param friends the friends
     * @param enemy_array the enemy_array
     * @param entities the entities
     * @return the move option[]
     */
    private MoveOption[] secondPass(CEntity self, int friends, ArrayList<Entity> enemy_array,
            ArrayList<Entity> entities) {
        MoveOption[] move_array = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();
        for (int j = 0; j < move_array.length && friends > 2; j++) {
            MoveOption option = move_array[j];
            for (int e = 0; e < enemy_array.size(); e++) {
                Entity en = enemy_array.get(e);
                CEntity enemy = centities.get(en);
                for (Entity other : entities) {
                    if (other.isEnemyOf(self.entity)) {
                        continue;
                    }
                    MoveOption foption = centities.get(other).current;
                    double threat_divisor = 1;
                    MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                    if (foption.getDamageInfo(enemy, false) != null) {
                        option.damage += (enemy.canMove() ? .1 : .2) * di.damage;
                        threat_divisor += foption.getCEntity().canMove() ? .4 : .6;
                    }
                    option.threat -= di.threat;
                    di.threat /= threat_divisor;
                    option.threat += di.threat;
                }
            }
        }
        return move_array;
    }

    /**
     * ******************************************************************** third pass, (not so bad)
     * oppurtunistic planner gives preference to good ranges/defensive positions based upon the mech
     * characterization ********************************************************************.
     *
     * @param self the self
     * @param enemy_array the enemy_array
     * @return the move option[]
     */
    private MoveOption[] thirdPass(CEntity self, ArrayList<Entity> enemy_array) {
        MoveOption[] move_array = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();

        for (int j = 0; j < move_array.length; j++) {
            MoveOption option = move_array[j];
            option.setState();
            double adjustment = 0;
            double temp_adjustment = 0;
            for (int e = 0; e < enemy_array.size(); e++) { // for each enemy
                Entity en = enemy_array.get(e);
                CEntity enemy = centities.get(en);
                int current_range = self.current.getFinalCoords().distance(
                        enemy.current.getFinalCoords());
                int range = option.getFinalCoords().distance(enemy.current.getFinalCoords());
                if (range > self.longRange) {
                    temp_adjustment += (!(range < enemy.longRange) ? .5 : 1)
                            * (1 + self.rangeDamages[self.range])
                            * (Math.max(range - self.longRange - .5
                                    * Math.max(self.jumpMP, .8 * self.runMP), 0));
                }
                if ((self.range == CEntity.RANGE_SHORT && (current_range > 5 || range > 9))
                        || (self.rangeDamages[CEntity.RANGE_SHORT] < 4 && current_range > 10)) {
                    temp_adjustment += ((enemy.range > CEntity.RANGE_SHORT) ? .5 : 1)
                            * (Math.max(1 + self.rangeDamages[CEntity.RANGE_SHORT], 5))
                            * Math.max(range - .5 * Math.max(self.jumpMP, .8 * self.runMP), 0);
                } else if (self.range == CEntity.RANGE_MEDIUM) {
                    temp_adjustment += ((current_range < 6 || current_range > 12) ? 1 : .25)
                            * ((enemy.range > CEntity.RANGE_SHORT) ? .5 : 1)
                            * (1 + self.rangeDamages[CEntity.RANGE_MEDIUM])
                            * Math.abs(range - .5 * Math.max(self.jumpMP, .8 * self.runMP));
                } else if (option.damage < .25 * self.rangeDamages[CEntity.RANGE_LONG]) {
                    temp_adjustment += ((range < 10) ? .25 : 1)
                            * (Math.max(1 + self.rangeDamages[CEntity.RANGE_LONG], 3))
                            * (1 / (1 + option.threat));
                }
                adjustment += Math.sqrt(temp_adjustment * enemy.bv / self.bv);
                // I would always like to face the opponent
                if (!(enemy.getEntity().isProne() || enemy.getEntity().isImmobile())
                        && CEntity.getThreatHitArc(option.getFinalCoords(),
                                option.getFinalFacing(), enemy.getEntity().getPosition()) != ToHitData.SIDE_FRONT) {
                    int fa = CEntity.getFiringAngle(option.getFinalCoords(), option
                            .getFinalFacing(), enemy.getEntity().getPosition());
                    if (fa > 90 && fa < 270) {
                        int distance = option.getFinalCoords().distance(
                                enemy.current.getFinalCoords());
                        double mod = 1;
                        if (fa > 130 && fa < 240)
                            mod = 2;
                        // big formula that says don't do it
                        mod *= ((Math.max(self.jumpMP, .8 * self.runMP) < 5) ? 2 : 1)
                                * ((double) self.bv / (double) 50)
                                * Math.sqrt(((double) self.bv) / enemy.bv)
                                / ((double) distance / 6 + 1);
                        option.self_threat += mod;
                        if (debug)
                            option.tv.add(mod + " " + fa + " Back to enemy\n");
                    }
                }
            }
            adjustment *= self.overall_armor_percent * self.strategy.attack / enemy_array.size();
            // fix for hiding in level 2 water
            // To a greedy bot, it always seems nice to stay in here...
            IHex h = game.getBoard().getHex(option.getFinalCoords());
            if (h.containsTerrain(Terrains.WATER)
                    && h.surface() > (self.getEntity().getElevation() + ((option.getFinalProne()) ? 0
                            : 1))) {
                double mod = (self.getEntity().heat + option.getMovementheatBuildup() <= 7) ? 100
                        : 30;
                adjustment += self.bv / mod;
            }
            // add them in now, then re-add them later
            if (self.range > CEntity.RANGE_SHORT) {
                int ele_dif = game.getBoard().getHex(option.getFinalCoords()).getElevation()
                        - game.getBoard().getHex(self.current.getFinalCoords()).getElevation();
                adjustment -= (Math.max(ele_dif, 0) + 1)
                        * ((double) Compute.getTargetTerrainModifier(game, option.getEntity())
                                .getValue() + 1);
            }

            // close the range if nothing else and healthy
            if (option.damage < .25 * self.rangeDamages[self.range]
                    && adjustment < self.rangeDamages[self.range]) {
                for (int e = 0; e < enemy_array.size(); e++) {
                    Entity en = enemy_array.get(e);
                    CEntity enemy = centities.get(en);
                    int range = option.getFinalCoords().distance(enemy.current.getFinalCoords());
                    if (range > 5)
                        adjustment += Math.pow(self.overall_armor_percent, 2)
                                * Math.sqrt((double) (range - 4) * enemy.bv / self.bv)
                                / enemy_array.size();
                }
            }

            if (option.damage < .25 * (1 + self.rangeDamages[self.range])) {
                option.self_threat += 2 * adjustment;
            } else if (option.damage < .5 * (1 + self.rangeDamages[self.range])) {
                option.self_threat += adjustment;
            }
            if (debug)
                option.tv.add(option.self_threat + " Initial Damage Adjustment " + "\n");
        }

        return move_array;
    }

    // pass should contains 30 ~ 60
    /**
     * ******************************************************************** fourth pass, speculation
     * on top moves use averaging to filter
     * ********************************************************************.
     *
     * @param self the self
     * @param enemy_array the enemy_array
     * @return the move option[]
     */
    private MoveOption[] fourthPass(CEntity self, ArrayList<Entity> enemy_array) {
        MoveOption[] move_array = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();
        for (int e = 0; e < enemy_array.size(); e++) { // for each enemy
            Entity en = enemy_array.get(e);
            CEntity enemy = centities.get(en);
            // engage in speculation on "best choices" when you loose iniative
            if (enemy.canMove()) {
                ArrayList<MoveOption> enemy_move_array = enemy.pass.getArray();
                ArrayList<MoveOption> to_check = new ArrayList<MoveOption>();
                // check some enemy moves
                for (int j = 0; j < move_array.length; j++) {
                    MoveOption option = null;
                    to_check.clear();
                    option = move_array[j];
                    option.setState();
                    // check for damning hexes specifically
                    // could also look at intervening defensive
                    ArrayList<Coords> coord = new ArrayList<Coords>();
                    Coords back = option.getFinalCoords().translated(
                            (option.getFinalFacing() + 3) % 6);
                    coord.add(back);
                    coord.add(back.translated((option.getFinalFacing() + 2) % 6));
                    coord.add(back.translated((option.getFinalFacing() + 4) % 6));
                    coord.add(option.getFinalCoords().translated((option.getFinalFacing())));
                    coord
                            .add(option.getFinalCoords().translated(
                                    (option.getFinalFacing() + 1) % 6));
                    coord
                            .add(option.getFinalCoords().translated(
                                    (option.getFinalFacing() + 2) % 6));
                    coord
                            .add(option.getFinalCoords().translated(
                                    (option.getFinalFacing() + 4) % 6));
                    coord
                            .add(option.getFinalCoords().translated(
                                    (option.getFinalFacing() + 5) % 6));
                    Iterator<Coords> ci = coord.iterator();
                    while (ci.hasNext()) {
                        Coords test = ci.next();
                        List<MoveOption> c = enemy.findMoves(test, this);
                        if (c.size() != 0)
                            to_check.addAll(c);
                    }
                    int range = option.getFinalCoords().distance(enemy.current.getFinalCoords());
                    int compare = 0;
                    if ((enemy.longRange) > range - Math.max(enemy.jumpMP, enemy.runMP)) {
                        compare = 30;
                    } else if (enemy.longRange > range) {
                        compare = 10;
                    }
                    double mod = this.enemies_moved / this.getEnemyEntities().size();
                    compare *= (1 + mod);
                    for (int k = 0; k <= compare && k < enemy_move_array.size(); k++) {
                        if (enemy_move_array.size() < compare) {
                            to_check.add(enemy_move_array.get(k));
                        } else {
                            int value = Compute.randomInt(enemy_move_array.size());
                            if (value % 2 == 1) {
                                to_check.add(enemy_move_array.get(value));
                            } else {
                                to_check.add(enemy_move_array.get(k));
                            }
                        }
                    }
                    Iterator<MoveOption> eo = to_check.iterator();
                    while (eo.hasNext()) {
                        MoveOption enemy_option = eo.next();
                        double max_threat = 0;
                        double max_damage = 0;
                        enemy_option.setState();
                        int enemy_hit_arc = CEntity.getThreatHitArc(enemy_option.getFinalCoords(),
                                enemy_option.getFinalFacing(), option.getFinalCoords());
                        int self_hit_arc = CEntity.getThreatHitArc(enemy_option.getFinalCoords(),
                                enemy_option.getFinalFacing(), option.getFinalCoords());
                        if (enemy_option.isJumping()) {
                            enemy_hit_arc = Compute.ARC_FORWARD;
                        }
                        int[] modifiers = option.getModifiers(enemy_option.getEntity());
                        if (modifiers[1] != TargetRoll.IMPOSSIBLE) {
                            self.engaged = true;
                            if (!enemy_option.isJumping()) {
                                max_threat = option.getMaxModifiedDamage(enemy_option,
                                        modifiers[1], modifiers[MoveOption.DEFENCE_PC]);
                            } else {
                                max_threat = .8 * enemy.getModifiedDamage(
                                        (modifiers[MoveOption.DEFENCE_PC] == 1) ? CEntity.TT
                                                : ToHitData.SIDE_FRONT,
                                        enemy_option.getFinalCoords().distance(
                                                option.getFinalCoords()), modifiers[1]);
                            }
                            max_threat = self.getThreatUtility(max_threat, self_hit_arc);
                        }
                        if (modifiers[0] != TargetRoll.IMPOSSIBLE) {
                            self.engaged = true;
                            max_damage = enemy_option.getMaxModifiedDamage(option, modifiers[0],
                                    modifiers[MoveOption.ATTACK_PC]);
                            max_damage = enemy.getThreatUtility(max_damage, enemy_hit_arc);
                            if (option.isPhysical) {
                                if (centities.get(option.getPhysicalTargetId()).getEntity().getId() == enemy
                                        .getEntity().getId()) {
                                    max_damage = option.getDamage(enemy);
                                } else {
                                    max_damage = 0;
                                }
                            }
                        }
                        MoveOption.DamageInfo di = option.getDamageInfo(enemy, true);
                        di.max_threat = Math.max(max_threat, di.max_threat);
                        di.min_damage = Math.min(di.min_damage, max_damage);
                        if (max_threat - max_damage > di.threat - di.damage) {
                            di.threat = max_threat;
                            di.damage = max_damage;
                            if (debug) {
                                option.tv.add(max_threat + " Spec Threat " + e + "\n");
                                option.tv.add(max_damage + " Spec Damage " + e + "\n");
                            }
                        }
                    }
                    // update estimates
                    option.damage = 0;
                    option.threat = 0;
                    for (Iterator<CEntity> i = option.damageInfos.keySet().iterator(); i.hasNext();) {
                        // my damage is the average of expected and min
                        CEntity cen = i.next();
                        // rescale
                        MoveOption.DamageInfo di = option.getDamageInfo(cen, true);
                        di.min_damage /= cen.strategy.target;
                        di.damage /= cen.strategy.target;
                        option.damage += (di.min_damage + di.damage) / 2;

                        // my threat is average of absolute worst, and expected
                        option.threat = Math.max(option.threat, di.max_threat + di.threat) / 2;
                        di.threat = (di.max_threat + 2 * di.threat) / 3;
                    }
                }
                // restore enemy
                enemy.current.setState();
            }
            self.current.setState();
        } // --end move speculation
        return move_array;
    }

    // pass should now be 20 ~ 40
    /**
     * ******************************************************************** fifth pass, final damage
     * and threat approximation --prevents moves that from the previous pass would cause the mech to
     * die ********************************************************************.
     *
     * @param self the self
     * @param enemy_array the enemy_array
     * @return the move option[]
     */
    private MoveOption[] fifthPass(CEntity self, ArrayList<Entity> enemy_array) {
        MoveOption[] move_array = self.pass.values().toArray(new MoveOption[0]);
        self.pass.clear();

        if (self.engaged) {
            for (int j = 0; j < move_array.length; j++) {
                MoveOption option = move_array[j];
                option.setState();
                GAAttack temp = this.bestAttack(option);
                if (temp != null) {
                    option.damage = (option.damage + temp.getFittestChromosomesFitness()) / 2;
                } else {
                    option.damage /= 2;
                }
                for (int e = 0; e < enemy_array.size(); e++) { // for each
                    // enemy
                    Entity en = enemy_array.get(e);
                    CEntity enemy = centities.get(en);
                    if (!enemy.canMove()) {
                        option.setThreat(enemy, (option.getThreat(enemy) + this.attackUtility(
                                enemy.current, self)) / 2);
                        if (debug)
                            option.tv.add(option.getThreat(enemy) + " Revised Threat " + e + " \n");
                        if (!option.isPhysical) {
                            if (temp != null) {
                                option.setDamage(enemy, (option.getDamage(enemy) + temp
                                        .getDamageUtility(enemy)) / 2);
                            } else {
                                // probably zero, but just in case
                                option.setDamage(enemy, option.getMinDamage(enemy));
                            }
                            if (debug)
                                option.tv.add(option.getDamage(enemy) + " Revised Damage " + e
                                        + " \n");
                            // this needs to be reworked
                            if (option.getFinalCoords().distance(enemy.current.getFinalCoords()) == 1) {
                                PhysicalOption p = PhysicalCalculator.getBestPhysicalAttack(option
                                        .getEntity(), enemy.getEntity(), game);
                                if (p != null) {
                                    option
                                            .setDamage(enemy, option.getDamage(enemy)
                                                    + p.expectedDmg);
                                    if (debug)
                                        option.tv.add(p.expectedDmg + " Physical Damage " + e
                                                + " \n");
                                }
                                p = PhysicalCalculator.getBestPhysicalAttack(enemy.getEntity(),
                                        option.getEntity(), game);
                                if (p != null) {
                                    option.setThreat(enemy, option.getThreat(enemy) + .5
                                            * p.expectedDmg);
                                    if (debug)
                                        option.tv.add(.5 * p.expectedDmg + " Physical Threat " + e
                                                + " \n");
                                }
                            }
                        }
                    } else if (!option.isPhysical) { // enemy can move (not
                        if (temp != null) {
                            option.setDamage(enemy, (2 * option.getDamage(enemy) + temp
                                    .getDamageUtility(enemy)) / 3);
                        } else {
                            option.setDamage(enemy, option.getMinDamage(enemy));
                        }
                    } else {
                        // get a more accurate estimate
                        option.setDamage(enemy, option.getDamage(enemy)
                                / Math.sqrt((double) enemy.bv / (double) self.bv));
                        option.damage = option.getDamage(enemy);
                    }
                }
                option.threat = 0;
                for (Iterator<MoveOption.DamageInfo> i = option.damageInfos.values().iterator(); i
                        .hasNext();) {
                    option.threat += i.next().threat;
                }
                if (debug) {
                    option.tv.add(option.threat + " Revised Threat Utility\n");
                    option.tv.add(option.damage + " Revised Damage Utility\n");
                }
            }
        }
        Arrays.<MoveOption> sort(move_array, new MoveOption.WeightedComparator(1, 1));
        self.current.setState();

        return move_array;
    }

    /**
     * Filter moves.
     *
     * @param move_array the move_array
     * @param pass the pass
     * @param comp the comp
     * @param filter the filter
     */
    private void filterMoves(MoveOption[] move_array, MoveOption.Table pass,
            MoveOption.WeightedComparator comp, int filter) {
        Arrays.sort(move_array, comp);

        // top 100 utility, mostly conservative
        for (int i = 0; i < filter && i < move_array.length; i++) {
            pass.put(move_array[i]);
        }
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#initFiring()
     */
    protected void initFiring() {
        ArrayList<Entity> entities = new ArrayList<Entity>(game.getEntitiesVector());
        for (int i = 0; i < entities.size(); i++) {
            Entity entity = entities.get(i);
            CEntity centity = centities.get(entity);
            centity.reset();
            centity.enemy_num = i;
        }
        for (Iterator<Entity> i = this.getEnemyEntities().iterator(); i.hasNext();) {
            Entity entity = i.next();
            CEntity centity = centities.get(entity);
            if (entity.isMakingDfa() || entity.isCharging()) {
                // try to prevent a physical attack from happening
                // but should take into account the toHit of the attack
                centity.strategy.target = 2.5;
            }
        }
    }

    /**
     * Calculate weapon attacks.
     *
     * @param en the en
     * @param mw the mw
     * @param best_only the best_only
     * @return the array list
     */
    protected ArrayList<AttackOption> calculateWeaponAttacks(Entity en, Mounted mw,
            boolean best_only) {
        int from = en.getId();
        int weaponID = en.getEquipmentNum(mw);
        int spin_mode = 0;
        int starg_mod;
        ArrayList<AttackOption> result = new ArrayList<AttackOption>();
        Enumeration<Entity> ents = game.getValidTargets(en).elements();
        WeaponAttackAction wep_test;
        WeaponType spinner;
        AttackOption a = null;
        AttackOption max = new AttackOption(null, null, 0, null, 1);
        while (ents.hasMoreElements()) {
            Entity e = ents.nextElement();
            CEntity enemy = centities.get(e);
            // long entry = System.currentTimeMillis();
            ToHitData th = WeaponAttackAction.toHit(game, from, e, weaponID);
            // long exit = System.currentTimeMillis();
            // if (exit != entry)
            // System.out.println("Weapon attack toHit took "+(exit-entry));
            if (th.getValue() != TargetRoll.IMPOSSIBLE && !(th.getValue() >= 13)) {
                double expectedDmg;

                wep_test = new WeaponAttackAction(from, e.getId(), weaponID);

                // If this is an Ultra or Rotary cannon, check for spin up
                spinner = (WeaponType) mw.getType();
                if ((spinner.getAmmoType() == AmmoType.T_AC_ULTRA)
                        || (spinner.getAmmoType() == AmmoType.T_AC_ULTRA_THB)
                        || (spinner.getAmmoType() == AmmoType.T_AC_ROTARY)) {
                    spin_mode = Compute.spinUpCannon(game, wep_test);
                    super.sendModeChange(from, weaponID, spin_mode);
                }

                // Ammo cycler runs each valid ammo type through the weapon
                // while
                // calling for expected damage on each type; best type by damage
                // is loaded

                expectedDmg = Compute.getAmmoAdjDamage(game, wep_test);

                // Get the secondary target modifier for this weapon/target
                // combo

                starg_mod = 1;

                if (en.getFacing() != -1) {
                    if (en.canChangeSecondaryFacing()) {

                        if (!Compute.isInArc(en.getPosition(), en.getSecondaryFacing(), e
                                .getPosition(), Compute.ARC_FORWARD)) {
                            starg_mod = 2;
                        }
                    } else {
                        if (!Compute.isInArc(en.getPosition(), en.getFacing(), e.getPosition(),
                                Compute.ARC_FORWARD)) {
                            starg_mod = 2;
                        }

                    }
                }

                // If the attacker is Mech, it may mount a specialty targeting
                // system
                if (en instanceof Mech) {
                    // Multi-trac I/II have no secondary target penalties
                    if (en.getTargSysType() == MiscType.T_TARGSYS_MULTI_TRAC
                            | en.getTargSysType() == MiscType.T_TARGSYS_MULTI_TRAC_II) {
                        starg_mod = 0;
                    }
                }

                // For good measure, infantry cannot attack multiple targets
                if (en instanceof Infantry && !(en instanceof BattleArmor)) {
                    starg_mod = 13;
                }

                a = new AttackOption(enemy, mw, expectedDmg, th, starg_mod);
                if (a.value > max.value) {
                    if (best_only) {
                        max = a;
                    } else {
                        result.add(0, a);
                    }
                } else {
                    result.add(a);
                }
            }
        }
        if (best_only && max.target != null) {
            result.add(max);
        }
        if (result.size() > 0) {
            result.add(new AttackOption(null, mw, 0, null, 1));
        }
        return result;
    }

    /**
     * Best attack.
     *
     * @param es the es
     * @return the gA attack
     */
    public GAAttack bestAttack(MoveOption es) {
        return bestAttack(es, null, 2);
    }

    /**
     * Best attack.
     *
     * @param es the es
     * @param target the target
     * @param search_level the search_level
     * @return the gA attack
     */
    public GAAttack bestAttack(MoveOption es, CEntity target, int search_level) {
        Entity en = es.getEntity();
        int attacks[] = new int[4];
        ArrayList<AttackOption> c = new ArrayList<AttackOption>();
        ArrayList<ArrayList<AttackOption>> front = new ArrayList<ArrayList<AttackOption>>();
        ArrayList<ArrayList<AttackOption>> left = new ArrayList<ArrayList<AttackOption>>();
        ArrayList<ArrayList<AttackOption>> right = new ArrayList<ArrayList<AttackOption>>();
        ArrayList<ArrayList<AttackOption>> rear = new ArrayList<ArrayList<AttackOption>>();
        GAAttack result = null;
        int o_facing = en.getFacing();
        double front_la_dmg = 0;
        double front_ra_dmg = 0;
        double left_la_dmg = 0;
        double left_ra_dmg = 0;
        double right_la_dmg = 0;
        double right_ra_dmg = 0;
        PhysicalOption best_front_po = new PhysicalOption(en);
        PhysicalOption best_left_po = new PhysicalOption(en);
        PhysicalOption best_right_po = new PhysicalOption(en);

        // Get best physical attack
        for (Mounted mw : en.getWeaponList()) {

            // If this weapon is in the same arm as a
            // brush off attack skip to next weapon.
            c = this.calculateWeaponAttacks(en, mw, true);

            // Get best physical attack
            best_front_po = PhysicalCalculator.getBestPhysical(en, game);

            if ((best_front_po != null) && (en instanceof Mech)) {

                // If this weapon is in the same arm as a brush off attack
                // skip to next weapon

                if (((best_front_po.type == PhysicalOption.BRUSH_LEFT) || (best_front_po.type == PhysicalOption.BRUSH_BOTH))
                        && (mw.getLocation() == Mech.LOC_LARM)) {
                    continue;
                }
                if (((best_front_po.type == PhysicalOption.BRUSH_RIGHT) || (best_front_po.type == PhysicalOption.BRUSH_BOTH))
                        && (mw.getLocation() == Mech.LOC_RARM)) {
                    continue;
                }

                // Total the damage of all weapons fired from each arm
                if (((best_front_po.type == PhysicalOption.PUNCH_LEFT) || (best_front_po.type == PhysicalOption.PUNCH_BOTH))
                        && (mw.getLocation() == Mech.LOC_LARM)) {
                    if (c.size() > 0) {
                        front_la_dmg += c.get(c.size() - 2).value;
                    }
                }
                if (((best_front_po.type == PhysicalOption.PUNCH_RIGHT) || (best_front_po.type == PhysicalOption.PUNCH_BOTH))
                        && (mw.getLocation() == Mech.LOC_RARM)) {
                    if (c.size() > 0) {
                        front_ra_dmg += c.get(c.size() - 2).value;
                    }
                }
                // If this weapon is a push attack and an arm mounted
                // weapon skip to next weapon

                if ((best_front_po.type == PhysicalOption.PUSH_ATTACK)
                        && ((mw.getLocation() == Mech.LOC_LARM) || (mw.getLocation() == Mech.LOC_RARM))) {
                    continue;
                }
            }

            // If this weapon is in the same arm as a punch
            // attack, add the damage to the running total.
            if (c.size() > 0) {
                front.add(c);
                attacks[0] = Math.max(attacks[0], c.size());
            }
            if (!es.getFinalProne() && en.canChangeSecondaryFacing()) {
                en.setSecondaryFacing((o_facing + 5) % 6);
                c = this.calculateWeaponAttacks(en, mw, true);
                if (c.size() > 0) {
                    // Get best physical attack
                    best_left_po = PhysicalCalculator.getBestPhysical(en, game);
                    if ((best_left_po != null) && (en instanceof Mech)) {
                        if (((best_left_po.type == PhysicalOption.PUNCH_LEFT) || (best_left_po.type == PhysicalOption.PUNCH_BOTH))
                                && (mw.getLocation() == Mech.LOC_LARM)) {
                            left_la_dmg += c.get(c.size() - 2).value;
                        }
                        if (((best_left_po.type == PhysicalOption.PUNCH_RIGHT) || (best_left_po.type == PhysicalOption.PUNCH_BOTH))
                                && (mw.getLocation() == Mech.LOC_RARM)) {
                            left_ra_dmg += c.get(c.size() - 2).value;
                        }
                    }
                    left.add(c);
                    attacks[1] = Math.max(attacks[1], c.size());
                }
                en.setSecondaryFacing((o_facing + 1) % 6);
                c = this.calculateWeaponAttacks(en, mw, true);
                if (c.size() > 0) {
                    // Get best physical attack
                    best_right_po = PhysicalCalculator.getBestPhysical(en, game);
                    if ((best_right_po != null) && (en instanceof Mech)) {
                        if (((best_right_po.type == PhysicalOption.PUNCH_LEFT) || (best_right_po.type == PhysicalOption.PUNCH_BOTH))
                                && (mw.getLocation() == Mech.LOC_LARM)) {
                            right_la_dmg += c.get(c.size() - 2).value;
                        }
                        if (((best_right_po.type == PhysicalOption.PUNCH_RIGHT) || (best_right_po.type == PhysicalOption.PUNCH_BOTH))
                                && (mw.getLocation() == Mech.LOC_RARM)) {
                            right_ra_dmg += c.get(c.size() - 2).value;
                        }
                    }
                    right.add(c);
                    attacks[2] = Math.max(attacks[2], c.size());
                }
                en.setSecondaryFacing((o_facing + 3) % 6);
                c = this.calculateWeaponAttacks(en, mw, true);
                if (c.size() > 0) {
                    rear.add(c);
                    attacks[3] = Math.max(attacks[3], c.size());
                }
            } else {
                attacks[1] = 0;
                attacks[2] = 0;
            }
            en.setSecondaryFacing(o_facing);
        }

        fireOrPhysicalCheck(best_front_po, en, front, front_la_dmg, front_ra_dmg);

        ArrayList<ArrayList<ArrayList<AttackOption>>> arcs = new ArrayList<ArrayList<ArrayList<AttackOption>>>();
        arcs.add(front);
        if (!es.getFinalProne() && en.canChangeSecondaryFacing()) {
            fireOrPhysicalCheck(best_left_po, en, left, left_la_dmg, left_ra_dmg);
            arcs.add(left);
            fireOrPhysicalCheck(best_right_po, en, right, right_la_dmg, right_ra_dmg);
            arcs.add(right);
            // Meks and protos can't twist all the way around.
            if (!(en instanceof Mech) && !(en instanceof Protomech)) {
                arcs.add(rear);
            }
        }
        for (int i = 0; i < arcs.size(); i++) {
            ArrayList<ArrayList<AttackOption>> v = arcs.get(i);
            if (v.size() > 0) {
                GAAttack test = new GAAttack(this, centities.get(en), v, Math.max(
                        (v.size() + attacks[i]) * search_level, 20 * search_level),
                        30 * search_level, en.isEnemyOf(getEntitiesOwned().get(0)));
                test.setFiringArc(i);
                test.evolve();
                if (target != null) {
                    if (result == null
                            || test.getDamageUtility(target) > result.getDamageUtility(target)) {
                        result = test;
                    }
                } else if (result == null
                        || test.getFittestChromosomesFitness() > result
                                .getFittestChromosomesFitness()) {
                    result = test;
                }
            }
        }
        return result;
    }

    /**
     * If the best attack is a punch, then check each punch damage against the weapons damage from
     * the appropriate arm; if the punch does more damage, drop the weapons in that arm to 0
     * expected damage Repeat this for left and right twists.
     *
     * @param best_po the best_po
     * @param entity the entity
     * @param attackOptions the attack options
     * @param la_dmg the la_dmg
     * @param ra_dmg the ra_dmg
     */
    private void fireOrPhysicalCheck(PhysicalOption best_po, Entity entity,
            ArrayList<ArrayList<AttackOption>> attackOptions, double la_dmg, double ra_dmg) {
        ArrayList<AttackOption> c;
        if ((best_po != null) && (entity instanceof Mech)) {
            if (best_po.type == PhysicalOption.PUNCH_LEFT) {
                if ((la_dmg < best_po.expectedDmg) && (attackOptions.size() > 0)) {
                    for (int i = 0; i < attackOptions.size(); i++) {
                        c = attackOptions.get(i);
                        for (int j = 0; j < c.size(); j++) {
                            if (c.get(j).weapon.getLocation() == Mech.LOC_LARM) {
                                c.get(j).expected = 0;
                                c.get(j).primaryExpected = 0;
                            }
                        }
                    }
                }
            }
            if (best_po.type == PhysicalOption.PUNCH_RIGHT) {
                if ((ra_dmg < best_po.expectedDmg) && (attackOptions.size() > 0)) {
                    for (int i = 0; i < attackOptions.size(); i++) {
                        c = attackOptions.get(i);
                        for (int j = 0; j < c.size(); j++) {
                            if (c.get(j).weapon.getLocation() == Mech.LOC_RARM) {
                                c.get(j).expected = 0;
                                c.get(j).primaryExpected = 0;
                            }
                        }
                    }
                }
            }
            if (best_po.type == PhysicalOption.PUNCH_BOTH) {
                if (((la_dmg + ra_dmg) < best_po.expectedDmg) && (attackOptions.size() > 0)) {
                    for (int i = 0; i < attackOptions.size(); i++) {
                        c = attackOptions.get(i);
                        for (int j = 0; j < c.size(); j++) {
                            if (c.get(j).weapon.getLocation() == Mech.LOC_LARM) {
                                c.get(j).expected = 0;
                                c.get(j).primaryExpected = 0;
                            }
                            if (c.get(j).weapon.getLocation() == Mech.LOC_RARM) {
                                c.get(j).expected = 0;
                                c.get(j).primaryExpected = 0;
                            }
                        }
                    }
                }
            }
        }
    }

    /* could use best of best strategy instead of expensive ga */
    /**
     * Attack utility.
     *
     * @param es the es
     * @param target the target
     * @return the double
     */
    public double attackUtility(MoveOption es, CEntity target) {
        GAAttack result = bestAttack(es, target, 1);
        if (result == null) {
            return 0;
        }
        return result.getFittestChromosomesFitness();
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#calculateFiringTurn()
     */
    public void calculateFiringTurn() {
        int first_entity = game.getFirstEntityNum(getMyTurn());
        int entity_num = first_entity;
        int best_entity = first_entity;
        int spin_mode = 0;
        double max = java.lang.Double.NEGATIVE_INFINITY;
        int[] results = null;
        ArrayList<ArrayList<AttackOption>> winner = null;
        int arc = 0;
        WeaponType spinner;

        if (entity_num == -1) {
            return;
        }

        do {
            Entity en = game.getEntity(entity_num);
            CEntity cen = centities.get(en);

            GAAttack test = bestAttack(cen.current, null, 3);

            if (test != null && test.getFittestChromosomesFitness() > max) {
                max = test.getFittestChromosomesFitness();
                results = test.getResultChromosome();
                arc = test.getFiringArc();
                best_entity = entity_num;
                winner = test.getAttack();
            }
            entity_num = game.getNextEntityNum(getMyTurn(), entity_num);
        } while (entity_num != first_entity && entity_num != -1);

        Vector<EntityAction> av = new Vector<EntityAction>();
        // maximum already selected (or default)
        Entity en = game.getEntity(best_entity);
        if (results != null) {
            Entity primary_target = game.getEntitiesVector().get(results[results.length - 1]);
            TreeSet<AttackOption> tm = new TreeSet<AttackOption>(new AttackOption.Sorter(centities
                    .get(primary_target)));
            for (int i = 0; i < results.length - 1; i++) {
                if (winner != null) {
                    AttackOption a = winner.get(i).get(results[i]);
                    if (a.target != null) {
                        a.target.expected_damage[a.toHit.getSideTable()] += a.value;
                        a.target.hasTakenDamage = true;
                        tm.add(a);
                    }
                }
            }
            Iterator<AttackOption> i = tm.iterator();
            while (i.hasNext()) {
                AttackOption a = i.next();

                WeaponAttackAction new_attack = new WeaponAttackAction(en.getId(), a.target
                        .getEntity().getId(), en.getEquipmentNum(a.weapon));

                if (en.getEquipment(new_attack.getWeaponId()).getLinked() != null) {
                    spinner = (WeaponType) a.weapon.getType();

                    // If this is an ultra-cannon or rotary cannon, try to spin
                    // it up

                    if ((spinner.getAmmoType() == AmmoType.T_AC_ULTRA)
                            || (spinner.getAmmoType() == AmmoType.T_AC_ULTRA_THB)
                            || (spinner.getAmmoType() == AmmoType.T_AC_ROTARY)) {
                        spin_mode = Compute.spinUpCannon(game, new_attack);
                        super.sendModeChange(en.getId(), en.getEquipmentNum(a.weapon), spin_mode);
                    }
                    Mounted cur_ammo = en.getEquipment(new_attack.getWeaponId()).getLinked();
                    new_attack.setAmmoId(en.getEquipmentNum(cur_ammo));
                    Compute.getAmmoAdjDamage(game, new_attack);

                }
                av.add(new_attack);

            }

            // Use the attack options and weapon attack actions to determine the
            // best aiming point

            if (av.size() > 0) {
                getAimPoint(tm, av);
            }

        }
        switch (arc) {
        case 1:
            av.add(0, new TorsoTwistAction(en.getId(), (en.getFacing() + 5) % 6));
            break;
        case 2:
            av.add(0, new TorsoTwistAction(en.getId(), (en.getFacing() + 1) % 6));
            break;
        case 3:
            av.add(0, new TorsoTwistAction(en.getId(), (en.getFacing() + 3) % 6));
            break;
        }
        sendAttackData(best_entity, av);
    }

    /**
     * consider how to put more pre-turn logic here.
     */
    protected void initMovement() {
        this.my_mechs_moved = 0;
        this.old_moves = null;
        this.enemies_moved = 0;
        double max_modifier = 1.4;
        ArrayList<Entity> entities = new ArrayList<Entity>(game.getEntitiesVector());
        double num_entities = Math.sqrt(entities.size()) / 100;
        ArrayList<CEntity> friends = new ArrayList<CEntity>();
        ArrayList<CEntity> foes = new ArrayList<CEntity>();
        double friend_sum = 0;
        double foe_sum = 0;
        double max_foe_bv = 0;
        CEntity max_foe = null;
        for (int i = 0; i < entities.size(); i++) {
            Entity entity = entities.get(i);
            CEntity centity = centities.get(entity);
            centity.enemy_num = i;
            double old_value = centity.bv * (centity.overall_armor_percent + 1);
            centity.reset(); // should get fresh values
            double new_value = centity.bv * (centity.overall_armor_percent + 1);
            double percent = 1 + (new_value - old_value) / old_value;
            if (entity.getOwner().equals(getLocalPlayer())) {
                friends.add(centity);
                friend_sum += new_value;
                if (percent < .85) {
                    // small retreat
                    centity.strategy.attack = .85;
                } else if (percent < .95) {
                    centity.strategy.attack = 1;
                } else if (percent <= 1 && centity.strategy.attack < max_modifier) {
                    if (percent == 1) {
                        if (centity.strategy.attack < 1) {
                            centity.strategy.attack = Math.min(1.4 * centity.strategy.attack, 1);
                        } else {
                            centity.strategy.attack *= (1.0 + num_entities);
                        }
                    } else {
                        centity.strategy.attack *= (1.0 + 2 * num_entities);
                    }
                }
            } else if (!entity.getOwner().isEnemyOf(getLocalPlayer())) {
                friend_sum += new_value;
            } else {
                foes.add(centity);
                foe_sum += new_value;
                if (entity.isCommander())
                    new_value *= 3; // make bots like to attack commanders
                if (new_value > max_foe_bv || max_foe == null) {
                    max_foe_bv = new_value;
                    max_foe = centity;
                }
                if (this.getEntitiesOwned().size() > 2) {
                    if (centity.strategy.target > 2) {
                        centity.strategy.target = 1 + .5 * (centity.strategy.target - 2);
                    }
                    if (percent < .85 && centity.strategy.target < max_modifier) {
                        centity.strategy.target *= (1.0 + 6 * num_entities);
                    } else if (percent < .95 && centity.strategy.target < max_modifier) {
                        centity.strategy.target *= (1.0 + 4 * num_entities);
                    } else if (percent <= 1) {
                        if (percent == 1) {
                            centity.strategy.target /= (1.0 + 2 * num_entities);
                        } else {
                            centity.strategy.target /= (1.0 + num_entities);
                        }
                    }
                    // don't go below one
                    if (centity.strategy.target < 1)
                        centity.strategy.target = 1;
                }
            }
        }
        System.out.println("Us " + friend_sum + " Them " + foe_sum);
        // do some more reasoning...
        double unit_values = friend_sum;
        double enemy_values = foe_sum;
        Iterator<CEntity> i = foes.iterator();

        if (friends.size() > 1) {
            if (Strategy.MainTarget == null
                    || null == game.getEntity(Strategy.MainTarget.getEntity().getId())) {
                Strategy.MainTarget = max_foe;
            }
            // TODO : Handle this better.
            if (null == Strategy.MainTarget)
                System.err.println("TestBot#initMovement() - no main target for bot");
            else if (null == Strategy.MainTarget.strategy)
                System.err.println("TestBot#initMovement() - no strategy for main target");
            else {
                Strategy.MainTarget.strategy.target += .2;
                while (i.hasNext()) {
                    CEntity centity = i.next();
                    // good turn, keep up the work, but randomize to reduce
                    // predictability
                    if (friend_sum - foe_sum >= .9 * unit_values - enemy_values) {
                        if (Compute.randomInt(2) == 1) {
                            centity.strategy.target += .3;
                        }
                        // lost that turn, but still in the fight, just get a
                        // little more aggressive
                    } else if (friend_sum > .9 * foe_sum) {
                        centity.strategy.target += .15;
                        // lost that turn and loosing
                    } else if (centity.strategy.target < 2) { // go for the
                        // gusto
                        centity.strategy.target += .3;
                    }
                    System.out.println(centity.getEntity().getShortName() + " "
                            + centity.strategy.target);
                }
            }
        }

        double ratio = friend_sum / foe_sum;
        double mod = 1;
        if (ratio < .9) {
            mod = .95;
        } else if (ratio < 1) {
            // no change
        } else { // attack
            mod = (1.0 + num_entities);
        }
        i = friends.iterator();
        while (i.hasNext()) {
            CEntity centity = i.next();
            if (!(mod < 1 && centity.strategy.attack < .6)
                    && !(mod > 1 && centity.strategy.attack >= max_modifier))
                centity.strategy.attack *= mod;
        }
        System.gc(); // just to make sure
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#processChat(megamek.common.event.GamePlayerChatEvent)
     */
    protected void processChat(GamePlayerChatEvent ge) {
        chatp.processChat(ge, this);
    }

    // Where do I put my units? This prioritizes hexes and facings
    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#calculateDeployment()
     */
    protected void calculateDeployment() {

        int weapon_count;
        int hex_count, x_ave, y_ave, nDir;
        double av_range;

        Coords pointing_to = new Coords();
        Entity test_ent;
        Enumeration<Entity> valid_attackers;

        int entNum = game.getFirstDeployableEntityNum();
        Coords[] cStart = getStartingCoordsArray();
        Coords cDeploy = getCoordsAround(getEntity(entNum), cStart);

        // Now that we have a location to deploy to, get a direction
        // Using average long range of deploying unit, point towards the largest
        // cluster of enemies in range

        av_range = 0.0;
        weapon_count = 0;
        for (Mounted mounted : getEntity(entNum).getWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();
            if ((wtype.getName() != "ATM 3") && (wtype.getName() != "ATM 6")
                    && (wtype.getName() != "ATM 9") && (wtype.getName() != "ATM 12")) {
                if (getEntity(entNum).getC3Master() != null) {
                    av_range += ((wtype.getLongRange()) * 1.25);
                } else {
                    av_range += wtype.getLongRange();
                }
                weapon_count++;
            }
        }
        for (Mounted mounted : getEntity(entNum).getAmmo()) {
            AmmoType atype = (AmmoType) mounted.getType();
            if (atype.getAmmoType() == AmmoType.T_ATM) {
                weapon_count++;
                av_range += 15.0;
                if (atype.getMunitionType() == AmmoType.M_HIGH_EXPLOSIVE) {
                    av_range -= 6;
                }
                if (atype.getMunitionType() == AmmoType.M_EXTENDED_RANGE) {
                    av_range += 12.0;
                }
            }
            if (atype.getAmmoType() == AmmoType.T_MML) {
                weapon_count++;
                if (atype.hasFlag(AmmoType.F_MML_LRM)) {
                    av_range = 9;
                } else {
                    av_range = 21.0;
                }
            }
        }

        av_range = av_range / weapon_count;

        hex_count = 0;
        x_ave = 0;
        y_ave = 0;
        valid_attackers = game.getEntities();
        while (valid_attackers.hasMoreElements()) {
            test_ent = valid_attackers.nextElement();
            if (test_ent.isDeployed()) {
                if (test_ent.isVisibleToEnemy()) {
                    if (cDeploy.distance(test_ent.getPosition()) <= (int) av_range) {
                        hex_count++;
                        x_ave += test_ent.getPosition().x;
                        y_ave += test_ent.getPosition().y;
                    }
                }
            }
        }
        if (hex_count != 0) {
            pointing_to = new Coords((x_ave / hex_count), (y_ave / hex_count));
        } else {
            pointing_to = new Coords(game.getBoard().getWidth() / 2,
                    game.getBoard().getHeight() / 2);
        }
        nDir = cDeploy.direction(pointing_to);

        // If unit has stealth armor, turn it on
        if (getEntity(entNum) instanceof Mech
                && getEntity(entNum).getArmorType() == EquipmentType.T_ARMOR_STEALTH) {
            for (Mounted test_equip : getEntity(entNum).getMisc()) {
                MiscType test_type = (MiscType) test_equip.getType();
                if (test_type.hasFlag(MiscType.F_STEALTH)) {
                    if (test_equip.curMode().getName() != "On") {
                        test_equip.setMode("On");
                        super.sendModeChange(entNum, getEntity(entNum).getEquipmentNum(test_equip),
                                1);
                    }
                }
            }
        }

        Entity ce = game.getEntity(entNum);
        megamek.debug.Assert.assertTrue(!ce.isHexProhibited(game.getBoard().getHex(cDeploy)));
        deploy(entNum, cDeploy, nDir);
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#continueMovementFor(megamek.common.Entity)
     */
    protected MovePath continueMovementFor(Entity entity) {
        System.out.println("Contemplating movement of " + entity.getShortName() + " "
                + entity.getId());
        CEntity cen = centities.get(entity);
        cen.refresh();
        firstPass(cen);

        Object[] enemy_array = this.getEnemyEntities().toArray();
        MoveOption result[] = calculateMove(entity);
        MoveOption min = null;
        ArrayList<MoveOption[]> possible = new ArrayList<MoveOption[]>();
        boolean short_circuit = false;

        if (result.length < 6) {
            min = result.length > 0 ? (MoveOption) result[0] : null;
            short_circuit = true;
        }
        possible.add(result);

        // should ignore mechs that are not engaged
        // and only do the below when there are 2 or mechs left to move
        if (!short_circuit) {
            if (this.getEntitiesOwned().size() > 1) {
                GALance lance = new GALance(this, possible, 50, 80);
                lance.evolve();
                min = lance.getResult();
                this.old_moves = lance;
            } else if (possible.get(0) != null && possible.get(0).length > 0) {
                min = possible.get(0)[0];
            }
        }
        if (min == null) {
            min = new MoveOption(game, centities.get(getFirstEntityNum()));
        }

        for (int d = 0; d < enemy_array.length; d++) {
            Entity en = (Entity) enemy_array[d];

            // ignore loaded units
            if (en.getPosition() == null) {
                continue;
            }

            CEntity enemy = centities.get(en);
            int enemy_hit_arc = CEntity.getThreatHitArc(enemy.current.getFinalCoords(),
                    enemy.current.getFinalFacing(), min.getFinalCoords());
            MoveOption.DamageInfo di = min.damageInfos.get(enemy);
            if (di != null) {
                enemy.expected_damage[enemy_hit_arc] += di.min_damage;
            }
            if (enemy.expected_damage[enemy_hit_arc] > 0) {
                enemy.hasTakenDamage = true;
            }
        }
        if (min.isPhysical) {
            centities.get(min.getPhysicalTargetId()).isPhysicalTarget = true;
        }
        System.out.println(min);
        min.getCEntity().current = min;
        min.getCEntity().last = min;
        this.my_mechs_moved++;
        min.getCEntity().moved = true;

        // If this unit has a jammed RAC, and it has only walked,
        // add an unjam action
        if (min.getLastStep() != null) {
            if (min.getCEntity().entity.canUnjamRAC()) {
                if ((min.getLastStep().getMovementType() == IEntityMovementType.MOVE_WALK)
                        || (min.getLastStep().getMovementType() == IEntityMovementType.MOVE_VTOL_WALK)
                        || (min.getLastStep().getMovementType() == IEntityMovementType.MOVE_NONE)) {
                    // Cycle through all available weapons, only unjam if the
                    // jam(med)
                    // RACs count for a significant portion of possible damage
                    int rac_damage = 0;
                    int other_damage = 0;
                    int clearance_range = 0;
                    for (Mounted equip : min.getCEntity().entity.getWeaponList()) {
                        WeaponType test_weapon = new WeaponType();

                        test_weapon = (WeaponType) equip.getType();
                        if ((test_weapon.getAmmoType() == AmmoType.T_AC_ROTARY)
                                && (equip.isJammed() == true)) {
                            rac_damage = rac_damage + 4 * (test_weapon.getDamage());
                        } else {
                            if (equip.canFire()) {
                                other_damage += test_weapon.getDamage();
                                if (test_weapon.getMediumRange() > clearance_range) {
                                    clearance_range = test_weapon.getMediumRange();
                                }
                            }
                        }
                    }
                    // Even if the jammed RAC doesn't make up a significant
                    // portion
                    // of the units damage, its still better to have it
                    // functional
                    // If nothing is "close" then unjam anyways
                    int check_range = 100;
                    for (Enumeration<Entity> unit_selection = game.getEntities(); unit_selection
                            .hasMoreElements();) {
                        Entity enemy = unit_selection.nextElement();
                        if ((min.getCEntity().entity.getPosition() != null)
                                && (enemy.getPosition() != null)
                                && (enemy.isEnemyOf(min.getCEntity().entity))) {
                            if (enemy.isVisibleToEnemy()) {
                                if (min.getCEntity().entity.getPosition().distance(
                                        enemy.getPosition()) < check_range) {
                                    check_range = min.getCEntity().entity.getPosition().distance(
                                            enemy.getPosition());
                                }
                            }
                        }
                    }
                    if ((rac_damage >= other_damage) || (check_range < clearance_range)) {
                        min.addStep(MovePath.STEP_UNJAM_RAC);
                    }
                }
            }
        }

        return min;
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#calculateMinefieldDeployment()
     */
    protected Vector<Minefield> calculateMinefieldDeployment() {
        Vector<Minefield> deployedMinefields = new Vector<Minefield>();

        deployMinefields(deployedMinefields, getLocalPlayer().getNbrMFConventional(), 0);
        deployMinefields(deployedMinefields, getLocalPlayer().getNbrMFCommand(), 1);
        deployMinefields(deployedMinefields, getLocalPlayer().getNbrMFVibra(), 2);

        return deployedMinefields;
    }

    /* (non-Javadoc)
     * @see megamek.client.bot.BotClient#calculateArtyAutoHitHexes()
     */
    protected PlayerIDandList<Coords> calculateArtyAutoHitHexes() {
        PlayerIDandList<Coords> artyAutoHitHexes = new PlayerIDandList<Coords>();
        artyAutoHitHexes.setPlayerID(this.getLocalPlayer().getId());
        return artyAutoHitHexes;
    }

    /**
     * Deploy minefields.
     *
     * @param deployedMinefields the deployed minefields
     * @param number the number
     * @param type the type
     */
    protected void deployMinefields(Vector<Minefield> deployedMinefields, int number, int type) {
        for (int i = 0; i < number; i++) {
            Coords coords = new Coords(Compute.randomInt(game.getBoard().getWidth()), Compute
                    .randomInt(game.getBoard().getHeight()));

            if (game.containsMinefield(coords)) {
                Minefield mf = game.getMinefields(coords).get(0);
                if (mf.getPlayerId() == getLocalPlayer().getId()) {
                    i--;
                    continue;
                }
            } else {
                Minefield mf = null;

                if (type == 0) {
                    mf = Minefield.createMinefield(coords, getLocalPlayer().getId(),
                            Minefield.TYPE_CONVENTIONAL, 10);
                } else if (type == 1) {
                    mf = Minefield.createMinefield(coords, getLocalPlayer().getId(),
                            Minefield.TYPE_COMMAND_DETONATED, 10);
                } else if (type == 2) {
                    mf = Minefield.createMinefield(coords, getLocalPlayer().getId(),
                            Minefield.TYPE_VIBRABOMB, 20);
                }
                deployedMinefields.add(mf);
            }
        }
    }

    // Calculate the best location to aim at on a target Mech. Attack options
    // must match
    // 1:1 with WeaponAttackActions in Vector.

    /**
     * Gets the aim point.
     *
     * @param attack_tree the attack_tree
     * @param atk_action_list the atk_action_list
     * @return the aim point
     */
    private void getAimPoint(TreeSet<AttackOption> attack_tree, Vector<EntityAction> atk_action_list) {

        if (attack_tree == null || atk_action_list == null) {
            return;
        }

        WeaponAttackAction aimed_attack;
        AttackOption current_option;

        Vector<Integer> target_id_list; // List of viable aimed-shot targets

        // Adjusted damages
        double base_damage, base_odds;
        double refactored_damage, refactored_head;

        // Armor values
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        double[] values = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

        // Internal structure values
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        double[] is_values = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

        // Fitness values
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        double[] fitness = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

        // Counters for armor penetration
        // Order is: head, ct, lt, rt, la, ra, ll, rl
        int[] pen_counters = { 0, 0, 0, 0, 0, 0, 0, 0 };

        int attacker_id, test_target;
        int action_index = 0;

        // Base to-hit
        int base_to_hit;

        // Best locations to aim for
        int best_loc, best_loc_head;

        boolean has_tcomp = false;
        boolean imob_target, rear_shot;
        boolean is_primary_target;

        // For each attack action

        target_id_list = new Vector<Integer>();
        for (EntityAction aea : atk_action_list) {

            if (aea instanceof WeaponAttackAction) {
                // Get the attacker

                attacker_id = ((WeaponAttackAction) atk_action_list.get(0)).getEntityId();

                // Check to see if the attacker has a tcomp

                has_tcomp = game.getEntity(attacker_id).hasTargComp();

                // Get the target entity id

                test_target = ((WeaponAttackAction) aea).getTargetId();

                // If the target is a Mech

                if (game.getEntity(test_target) instanceof Mech) {

                    // If the target is officially immobile or if the attacker
                    // has a tcomp

                    if ((has_tcomp == true) | (game.getEntity(test_target).isImmobile())) {
                        if (!target_id_list.contains(test_target)) {
                            target_id_list.add(test_target);
                        }
                    }
                }
            }
        }

        // For each valid target

        is_primary_target = true;
        for (Iterator<Integer> i = target_id_list.iterator(); i.hasNext();) {

            // Set the current target

            test_target = i.next();
            imob_target = game.getEntity(test_target).isImmobile();

            // Get the targets aspect ratio

            rear_shot = false;
            for (Iterator<AttackOption> j = attack_tree.iterator(); j.hasNext();) {
                current_option = j.next();
                if (current_option.target.getEntity().getId() == test_target) {
                    int attack_direction = current_option.toHit.getSideTable();
                    if (attack_direction == ToHitData.SIDE_REAR) {
                        rear_shot = true;
                    } else {
                        rear_shot = false;
                    }
                    break;
                }
            }

            // Get the armor values for the target and make them negative (count
            // up)

            values[0] = game.getEntity(test_target).getArmor(Mech.LOC_HEAD);
            values[1] = game.getEntity(test_target).getArmor(Mech.LOC_CT, rear_shot);
            values[2] = game.getEntity(test_target).getArmor(Mech.LOC_LT, rear_shot);
            values[3] = game.getEntity(test_target).getArmor(Mech.LOC_RT, rear_shot);
            values[4] = game.getEntity(test_target).getArmor(Mech.LOC_LARM);
            values[5] = game.getEntity(test_target).getArmor(Mech.LOC_RARM);
            values[6] = game.getEntity(test_target).getArmor(Mech.LOC_LLEG);
            values[7] = game.getEntity(test_target).getArmor(Mech.LOC_RLEG);

            // Get the internals for the target

            is_values[0] = game.getEntity(test_target).getInternal(Mech.LOC_HEAD);
            is_values[1] = game.getEntity(test_target).getInternal(Mech.LOC_CT);
            is_values[2] = game.getEntity(test_target).getInternal(Mech.LOC_LT);
            is_values[3] = game.getEntity(test_target).getInternal(Mech.LOC_RT);
            is_values[4] = game.getEntity(test_target).getInternal(Mech.LOC_LARM);
            is_values[5] = game.getEntity(test_target).getInternal(Mech.LOC_RARM);
            is_values[6] = game.getEntity(test_target).getInternal(Mech.LOC_LLEG);
            is_values[7] = game.getEntity(test_target).getInternal(Mech.LOC_RLEG);

            // Reset the fitness array
            for (int arr_index = 0; arr_index < 8; arr_index++) {
                fitness[arr_index] = 0.0;
            }

            // Reset the penetration counter

            for (int arr_index = 0; arr_index < 8; arr_index++) {
                pen_counters[arr_index] = 0;
            }

            // For each attack option

            action_index = 0;
            refactored_damage = 0.0;
            refactored_head = 0.0;

            best_loc = Mech.LOC_CT;
            best_loc_head = Mech.LOC_CT;
            for (Iterator<AttackOption> j = attack_tree.iterator(); j.hasNext();) {

                // If the target of the attack option is the current target

                current_option = j.next();
                if (test_target == current_option.target.getEntity().getId()) {

                    // Get the weapon

                    Mounted test_weapon = current_option.weapon;

                    // If the weapon is not LBX cannon or LBX cannon loaded with
                    // slug

                    boolean direct_fire = true;
                    if (((WeaponType) test_weapon.getType()).hasFlag(WeaponType.F_DIRECT_FIRE) == false) {
                        direct_fire = false;
                    }
                    if (test_weapon.getType().hasFlag(WeaponType.F_PULSE)) {
                        direct_fire = false;
                    }
                    if ((((WeaponType) test_weapon.getType()).getAmmoType() == AmmoType.T_AC_LBX)
                            || (((WeaponType) test_weapon.getType()).getAmmoType() == AmmoType.T_AC_LBX)) {
                        if (((AmmoType) test_weapon.getLinked().getType()).getAmmoType() == AmmoType.M_CLUSTER) {
                            direct_fire = false;
                        }
                    }
                    if (test_weapon.getCurrentShots() > 1) {
                        direct_fire = false;
                    }

                    // If the weapon is direct fire

                    if (direct_fire == true) {

                        // Get the expected damage, to-hit number, and odds
                        // (0-1) of hitting

                        base_damage = is_primary_target ? current_option.primaryExpected
                                : current_option.expected;
                        base_to_hit = is_primary_target ? current_option.toHit.getValue()
                                : current_option.toHit.getValue() + 1;
                        base_odds = is_primary_target ? current_option.primaryOdds
                                : current_option.odds;
                        base_damage = base_odds == 0.0 ? 0.0 : base_damage / base_odds;

                        // If the target is mobile, only a tcomp can make an
                        // aimed shot

                        if (!imob_target & has_tcomp) {

                            // Refactor the expected damage to account for
                            // increased to-hit number

                            refactored_head = 0.0;
                            if (base_to_hit + 4 <= 12) {
                                refactored_damage = base_damage
                                        * (Compute.oddsAbove(base_to_hit + 4) / 100.0);
                                ((WeaponAttackAction) atk_action_list.get(action_index))
                                        .setAimingMode(IAimingModes.AIM_MODE_TARG_COMP);
                                // Consider that a regular shot has a roughly
                                // 20% chance of hitting the same location
                                // Use the better of the regular shot or aimed
                                // shot
                                if (0.2 * base_damage * (Compute.oddsAbove(base_to_hit) / 100.0) > refactored_damage) {
                                    refactored_damage = 0.2 * base_damage
                                            * (Compute.oddsAbove(base_to_hit) / 100.0);
                                    ((WeaponAttackAction) atk_action_list.get(action_index))
                                            .setAimingMode(IAimingModes.AIM_MODE_NONE);
                                }
                            } else {
                                refactored_damage = 0.0;
                                ((WeaponAttackAction) atk_action_list.get(action_index))
                                        .setAimingMode(IAimingModes.AIM_MODE_NONE);
                            }

                        }

                        // If the target is immobile, the shot will always be
                        // aimed

                        if (imob_target) {

                            // If the attacker has a tcomp, consider both
                            // options: immobile aim, tcomp aim

                            if (has_tcomp) {

                                // Refactor the expected damage to account for
                                // increased to-hit number of the tcomp

                                refactored_damage = base_damage
                                        * (Compute.oddsAbove(base_to_hit + 4) / 100.0);
                                refactored_head = 0.0;
                                ((WeaponAttackAction) atk_action_list.get(action_index))
                                        .setAimingMode(IAimingModes.AIM_MODE_TARG_COMP);

                                // Check against immobile aim mode w/tcomp
                                // assist
                                if (0.50 * base_damage * (Compute.oddsAbove(base_to_hit) / 100.0) > refactored_damage) {
                                    refactored_damage = 0.50 * base_damage
                                            * (Compute.oddsAbove(base_to_hit) / 100.0);
                                    refactored_head = 0.50 * base_damage
                                            * (Compute.oddsAbove(base_to_hit + 7) / 100.0);
                                    ((WeaponAttackAction) atk_action_list.get(action_index))
                                            .setAimingMode(IAimingModes.AIM_MODE_IMMOBILE);
                                }

                            } else {

                                // If the attacker doesn't have a tcomp, settle
                                // for immobile aim

                                refactored_damage = 0.50 * base_damage
                                        * (Compute.oddsAbove(base_to_hit) / 100.0);
                                refactored_head = 0.50 * base_damage
                                        * (Compute.oddsAbove(base_to_hit + 7) / 100.0);
                                ((WeaponAttackAction) atk_action_list.get(action_index))
                                        .setAimingMode(IAimingModes.AIM_MODE_IMMOBILE);

                            }
                        }

                        // Count the refactored damage off each location. Count
                        // hits to IS.
                        // Ignore locations that have been previously destroyed

                        for (int arr_index = 0; arr_index < 8; arr_index++) {
                            if (arr_index == 0) {
                                values[arr_index] -= refactored_head;
                            } else {
                                values[arr_index] -= refactored_damage;
                            }
                            if (values[arr_index] < 0 & is_values[arr_index] > 0) {
                                is_values[arr_index] += values[arr_index];
                                values[arr_index] = 0;
                                pen_counters[arr_index]++;
                            }
                        }
                    }

                    // End if (AttackAction against current target)

                }

                action_index++;

            }

            double loc_mod;
            for (int arr_index = 0; arr_index < 8; arr_index++) {
                loc_mod = 0.0;

                // If any location has had its armor stripped but is not
                // destroyed,
                // criticals may result

                if (values[arr_index] <= 0 & is_values[arr_index] > 0) {
                    switch (arr_index) {
                    case 0: // Head hits are very good, pilot damage and
                        // critical systems
                        fitness[arr_index] = 4.0 * pen_counters[arr_index];
                        fitness[arr_index] += getAimModifier(test_target, Mech.LOC_HEAD);
                        break;
                    case 1: // CT hits are good, chances at hitting gyro,
                        // engine
                        fitness[arr_index] = 3.0 * pen_counters[arr_index];
                        fitness[arr_index] += getAimModifier(test_target, Mech.LOC_CT);
                        break;
                    case 2: // Side torso hits are good, equipment hits and
                        // ammo slots
                        loc_mod = getAimModifier(test_target, Mech.LOC_LT);
                        fitness[arr_index] = 2.0 * pen_counters[arr_index];
                        fitness[arr_index] += loc_mod;
                        break;
                    case 3:
                        loc_mod = getAimModifier(test_target, Mech.LOC_RT);
                        fitness[arr_index] = 2.0 * pen_counters[arr_index];
                        fitness[arr_index] += loc_mod;
                        break;
                    case 6: // Leg hits are good, reduces target mobility
                        loc_mod = getAimModifier(test_target, Mech.LOC_LLEG);
                        fitness[arr_index] = 2.0 * pen_counters[arr_index];
                        fitness[arr_index] += loc_mod;
                        break;
                    case 7:
                        loc_mod = getAimModifier(test_target, Mech.LOC_RLEG);
                        fitness[arr_index] = 2.0 * pen_counters[arr_index];
                        fitness[arr_index] += loc_mod;
                        break;
                    case 4: // Arm hits might damage some weapons, but not
                        // the best option
                        loc_mod = getAimModifier(test_target, Mech.LOC_LARM);
                        fitness[arr_index] = pen_counters[arr_index];
                        fitness[arr_index] += loc_mod;
                        break;
                    case 5:
                        loc_mod = getAimModifier(test_target, Mech.LOC_RARM);
                        fitness[arr_index] = pen_counters[arr_index];
                        fitness[arr_index] += loc_mod;
                    }
                }

                // If any location has been destroyed, adjust the location value
                // relative to its value

                if (is_values[arr_index] <= 0 & pen_counters[arr_index] > 0) {

                    switch (arr_index) {
                    case 0: // Destroying the head is a hard kill and gets
                        // rid of the pilot, too
                        fitness[arr_index] += 3 * getAimModifier(test_target, Mech.LOC_HEAD);
                        break;
                    case 1: // Destroying the CT is a hard kill
                        fitness[arr_index] += 2 * getAimModifier(test_target, Mech.LOC_CT);
                        break;
                    case 2: // Destroying a side torso could be a soft kill
                        // or cripple
                        fitness[arr_index] += 1.5 * getAimModifier(test_target, Mech.LOC_LT);
                        break;
                    case 3:
                        fitness[arr_index] += 1.5 * getAimModifier(test_target, Mech.LOC_RT);
                        break;
                    case 6: // Destroying a leg is a mobility kill
                        fitness[arr_index] += 1.5 * getAimModifier(test_target, Mech.LOC_LLEG);
                        break;
                    case 7:
                        fitness[arr_index] += 1.5 * getAimModifier(test_target, Mech.LOC_RLEG);
                        break;
                    case 4: // Destroying an arm can cripple a Mech, but not
                        // the best option
                        fitness[arr_index] += getAimModifier(test_target, Mech.LOC_LARM);
                        break;
                    case 5:
                        fitness[arr_index] += getAimModifier(test_target, Mech.LOC_RARM);
                        break;

                    }
                }

            }

            // Get the best target location, including the head

            refactored_damage = fitness[1];
            for (int arr_index = 0; arr_index < 8; arr_index++) {
                if (fitness[arr_index] > refactored_damage) {
                    refactored_damage = fitness[arr_index];
                    switch (arr_index) {
                    case 0:
                        best_loc_head = Mech.LOC_HEAD;
                        break;
                    case 2: // case 1 is CT, which was initialized as
                        // default
                        best_loc_head = Mech.LOC_LT;
                        break;
                    case 3:
                        best_loc_head = Mech.LOC_RT;
                        break;
                    case 4:
                        best_loc_head = Mech.LOC_LARM;
                        break;
                    case 5:
                        best_loc_head = Mech.LOC_RARM;
                        break;
                    case 6:
                        best_loc_head = Mech.LOC_LLEG;
                        break;
                    case 7:
                        best_loc_head = Mech.LOC_RLEG;
                        break;
                    default:
                        best_loc_head = Mech.LOC_CT;
                    }
                }
            }

            // Get the best target location, not including the head
            int temp_index = 1;
            refactored_damage = fitness[1];
            for (int arr_index = 2; arr_index < 8; arr_index++) {
                if (fitness[arr_index] > refactored_damage) {
                    refactored_damage = fitness[arr_index];
                    temp_index = arr_index;
                    switch (arr_index) {
                    case 2: // case 1 is CT, which was set as default
                        best_loc = Mech.LOC_LT;
                        break;
                    case 3:
                        best_loc = Mech.LOC_RT;
                        break;
                    case 4:
                        best_loc = Mech.LOC_LARM;
                        break;
                    case 5:
                        best_loc = Mech.LOC_RARM;
                        break;
                    case 6:
                        best_loc = Mech.LOC_LLEG;
                        break;
                    case 7:
                        best_loc = Mech.LOC_RLEG;
                        break;
                    default:
                        best_loc = Mech.LOC_CT;
                    }
                }
            }

            // For all weapon attack actions

            for (Iterator<EntityAction> j = atk_action_list.iterator(); j.hasNext();) {
                aimed_attack = (WeaponAttackAction) j.next();

                // If the target of the action is the current target

                if (aimed_attack.getTargetId() == test_target) {

                    // If the weapon aim mode is set to use a tcomp

                    if (aimed_attack.getAimingMode() == IAimingModes.AIM_MODE_TARG_COMP) {

                        // If the location is at least close to being breached
                        // or the target is immobile

                        if (values[temp_index] <= Compute.randomInt(5)) {
                            aimed_attack.setAimedLocation(best_loc);
                        } else {
                            aimed_attack.setAimingMode(IAimingModes.AIM_MODE_NONE);
                            aimed_attack.setAimedLocation(Entity.LOC_NONE);
                        }

                    }

                    // If the weapon aim mode is set for immobile aim

                    if (aimed_attack.getAimingMode() == IAimingModes.AIM_MODE_IMMOBILE) {
                        aimed_attack.setAimedLocation(best_loc_head);
                    }

                }

            }

            // Any targets after this are secondary targets. Use secondary odds
            // and damage.

            is_primary_target = false;
        }
    }

    /**
     * Gets the aim modifier.
     *
     * @param target_id the target_id
     * @param location the location
     * @return the aim modifier
     */
    private double getAimModifier(int target_id, int location) {

        double loc_total;

        // TODO: change the factor of 0.1 to float depending on critical item
        // type

        loc_total = 0.1 * game.getEntity(target_id).getHittableCriticals(location);

        return loc_total;
    }
}
